<?php
    // +----------------------------------------------------------------------
    // | ThinkPHP [ WE CAN DO IT JUST THINK ]
    // +----------------------------------------------------------------------
    // | Copyright (c) 2006~2017 http://thinkphp.cn All rights reserved.
    // +----------------------------------------------------------------------
    // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
    // +----------------------------------------------------------------------
    // | Author: liu21st <liu21st@gmail.com>
    // +----------------------------------------------------------------------
    namespace think;

    use think\exception\HttpException;
    use think\exception\HttpResponseException;
    use think\exception\RouteNotFoundException;

    /**
     * App 应用管理
     * @author  liu21st <liu21st@gmail.com>
     */
    class App {
        /**
         * @var string 当前模块路径
         */
        public static $modulePath;
        /**
         * @var bool 应用调试模式
         */
        public static $debug = true;
        /**
         * @var string 应用类库命名空间
         */
        public static $namespace = 'app';
        /**
         * @var bool 应用类库后缀
         */
        public static $suffix = false;
        /**
         * @var bool 是否初始化过
         */
        protected static $init = false;
        /**
         * @var bool 应用路由检测
         */
        protected static $routeCheck;
        /**
         * @var bool 严格路由检测
         */
        protected static $routeMust;
        protected static $dispatch;
        protected static $file = [];

        /**
         * 执行应用程序
         * @access public
         * @param Request $request Request对象
         * @return Response
         * @throws Exception
         */
        public static function run(Request $request = null) {
            is_null($request) && $request = Request::instance();
            try {
                $config = self::initCommon();
                if (defined('BIND_MODULE')) {
                    // 模块/控制器绑定
                    BIND_MODULE && Route::bind(BIND_MODULE);
                } elseif ($config['auto_bind_module']) {
                    // 入口自动绑定
                    $name = pathinfo($request->baseFile(), PATHINFO_FILENAME);
                    if ($name && 'index' != $name && is_dir(APP_PATH . $name)) {
                        Route::bind($name);
                    }
                }
                $request->filter($config['default_filter']);
                // 默认语言
                Lang::range($config['default_lang']);
                if ($config['lang_switch_on']) {
                    // 开启多语言机制 检测当前语言
                    Lang::detect();
                }
                $request->langset(Lang::range());
                // 加载系统语言包
                Lang::load([
                               THINK_PATH . 'lang' . DS . $request->langset() . EXT,
                               APP_PATH . 'lang' . DS . $request->langset() . EXT,
                           ]
                );
                // 获取应用调度信息
                $dispatch = self::$dispatch;
                if (empty($dispatch)) {
                    // 进行URL路由检测
                    $dispatch = self::routeCheck($request, $config);
                }
                // 记录当前调度信息
                $request->dispatch($dispatch);
                // 记录路由和请求信息
                if (self::$debug) {
                    Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info');
                    Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info');
                    Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info');
                }
                // 监听app_begin
                Hook::listen('app_begin', $dispatch);
                // 请求缓存检查
                $request->cache($config['request_cache'], $config['request_cache_expire'], $config['request_cache_except']);
                $data = self::exec($dispatch, $config);
            } catch (HttpResponseException $exception) {
                $data = $exception->getResponse();
            }
            // 清空类的实例化
            Loader::clearInstance();
            // 输出数据到客户端
            if ($data instanceof Response) {
                $response = $data;
            } elseif (!is_null($data)) {
                // 默认自动识别响应输出类型
                $isAjax   = $request->isAjax();
                $type     = $isAjax ? Config::get('default_ajax_return') : Config::get('default_return_type');
                $response = Response::create($data, $type);
            } else {
                $response = Response::create();
            }
            // 监听app_end
            Hook::listen('app_end', $response);
            return $response;
        }

        /**
         * 初始化应用
         */
        public static function initCommon() {
            if (empty(self::$init)) {
                if (defined('APP_NAMESPACE')) {
                    self::$namespace = APP_NAMESPACE;
                }
                Loader::addNamespace(self::$namespace, APP_PATH);
                // 初始化应用
                $config       = self::init();
                self::$suffix = $config['class_suffix'];
                // 应用调试模式
                self::$debug = Env::get('app_debug', Config::get('app_debug'));
                if (!self::$debug) {
                    ini_set('display_errors', 'Off');
                } elseif (!IS_CLI) {
                    //重新申请一块比较大的buffer
                    if (ob_get_level() > 0) {
                        $output = ob_get_clean();
                    }
                    ob_start();
                    if (!empty($output)) {
                        echo $output;
                    }
                }
                if (!empty($config['root_namespace'])) {
                    Loader::addNamespace($config['root_namespace']);
                }
                // 加载额外文件
                if (!empty($config['extra_file_list'])) {
                    foreach ($config['extra_file_list'] as $file) {
                        $file = strpos($file, '.') ? $file : APP_PATH . $file . EXT;
                        if (is_file($file) && !isset(self::$file[$file])) {
                            include $file;
                            self::$file[$file] = true;
                        }
                    }
                }
                // 设置系统时区
                date_default_timezone_set($config['default_timezone']);
                // 监听app_init
                Hook::listen('app_init');
                self::$init = true;
            }
            return Config::get();
        }

        /**
         * 初始化应用或模块
         * @access public
         * @param string $module 模块名
         * @return array
         */
        private static function init($module = '') {
            // 定位模块目录
            $module = $module ? $module . DS : '';
            // 加载初始化文件
            if (is_file(APP_PATH . $module . 'init' . EXT)) {
                include APP_PATH . $module . 'init' . EXT;
            } elseif (is_file(RUNTIME_PATH . $module . 'init' . EXT)) {
                include RUNTIME_PATH . $module . 'init' . EXT;
            } else {
                $path = APP_PATH . $module;
                // 加载模块配置
                $config = Config::load(CONF_PATH . $module . 'config' . CONF_EXT);
                // 读取数据库配置文件
                $filename = CONF_PATH . $module . 'database' . CONF_EXT;
                Config::load($filename, 'database');
                // 读取扩展配置文件
                if (is_dir(CONF_PATH . $module . 'extra')) {
                    $dir   = CONF_PATH . $module . 'extra';
                    $files = scandir($dir);
                    foreach ($files as $file) {
                        if (strpos($file, CONF_EXT)) {
                            $filename = $dir . DS . $file;
                            Config::load($filename, pathinfo($file, PATHINFO_FILENAME));
                        }
                    }
                }
                // 加载应用状态配置
                if ($config['app_status']) {
                    $config = Config::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT);
                }
                // 加载行为扩展文件
                if (is_file(CONF_PATH . $module . 'tags' . EXT)) {
                    Hook::import(include CONF_PATH . $module . 'tags' . EXT);
                }
                // 加载公共文件
                if (is_file($path . 'common' . EXT)) {
                    include $path . 'common' . EXT;
                }
                // 加载当前模块语言包
                if ($module) {
                    Lang::load($path . 'lang' . DS . Request::instance()->langset() . EXT);
                }
            }
            return Config::get();
        }

        /**
         * URL路由检测（根据PATH_INFO)
         * @access public
         * @param  \think\Request $request
         * @param  array          $config
         * @return array
         * @throws \think\Exception
         */
        public static function routeCheck($request, array $config) {
            $path   = $request->path();
            $depr   = $config['pathinfo_depr'];
            $result = false;
            // 路由检测
            $check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on'];
            if ($check) {
                // 开启路由
                if (is_file(RUNTIME_PATH . 'route.php')) {
                    // 读取路由缓存
                    $rules = include RUNTIME_PATH . 'route.php';
                    if (is_array($rules)) {
                        Route::rules($rules);
                    }
                } else {
                    $files = $config['route_config_file'];
                    foreach ($files as $file) {
                        if (is_file(CONF_PATH . $file . CONF_EXT)) {
                            // 导入路由配置
                            $rules = include CONF_PATH . $file . CONF_EXT;
                            if (is_array($rules)) {
                                Route::import($rules);
                            }
                        }
                    }
                }
                // 路由检测（根据路由定义返回不同的URL调度）
                $result = Route::check($request, $path, $depr, $config['url_domain_deploy']);
                $must   = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must'];
                if ($must && false === $result) {
                    // 路由无效
                    throw new RouteNotFoundException();
                }
            }
            if (false === $result) {
                // 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索
                $result = Route::parseUrl($path, $depr, $config['controller_auto_search']);
            }
            return $result;
        }

        protected static function exec($dispatch, $config) {
            switch ($dispatch['type']) {
                case 'redirect':
                    // 执行重定向跳转
                    $data = Response::create($dispatch['url'], 'redirect')->code($dispatch['status']);
                    break;
                case 'module':
                    // 模块/控制器/操作
                    $data = self::module($dispatch['module'], $config, isset($dispatch['convert']) ? $dispatch['convert'] : null);
                    break;
                case 'controller':
                    // 执行控制器操作
                    $vars = array_merge(Request::instance()->param(), $dispatch['var']);
                    $data = Loader::action($dispatch['controller'], $vars, $config['url_controller_layer'], $config['controller_suffix']);
                    break;
                case 'method':
                    // 执行回调方法
                    $vars = array_merge(Request::instance()->param(), $dispatch['var']);
                    $data = self::invokeMethod($dispatch['method'], $vars);
                    break;
                case 'function':
                    // 执行闭包
                    $data = self::invokeFunction($dispatch['function']);
                    break;
                case 'response':
                    $data = $dispatch['response'];
                    break;
                default:
                    throw new \InvalidArgumentException('dispatch type not support');
            }
            return $data;
        }

        /**
         * 执行模块
         * @access public
         * @param array $result  模块/控制器/操作
         * @param array $config  配置参数
         * @param bool  $convert 是否自动转换控制器和操作名
         * @return mixed
         */
        public static function module($result, $config, $convert = null) {
            if (is_string($result)) {
                $result = explode('/', $result);
            }
            $request = Request::instance();
            if ($config['app_multi_module']) {
                // 多模块部署
                $module    = strip_tags(strtolower($result[0] ?: $config['default_module']));
                $bind      = Route::getBind('module');
                $available = false;
                if ($bind) {
                    // 绑定模块
                    list($bindModule) = explode('/', $bind);
                    if (empty($result[0])) {
                        $module    = $bindModule;
                        $available = true;
                    } elseif ($module == $bindModule) {
                        $available = true;
                    }
                } elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) {
                    $available = true;
                }
                // 模块初始化
                if ($module && $available) {
                    // 初始化模块
                    $request->module($module);
                    $config = self::init($module);
                    // 模块请求缓存检查
                    $request->cache($config['request_cache'], $config['request_cache_expire'], $config['request_cache_except']);
                } else {
                    throw new HttpException(404, 'module not exists:' . $module);
                }
            } else {
                // 单一模块部署
                $module = '';
                $request->module($module);
            }
            // 当前模块路径
            App::$modulePath = APP_PATH . ($module ? $module . DS : '');
            // 是否自动转换控制器和操作名
            $convert = is_bool($convert) ? $convert : $config['url_convert'];
            // 获取控制器名
            $controller = strip_tags($result[1] ?: $config['default_controller']);
            $controller = $convert ? strtolower($controller) : $controller;
            // 获取操作名
            $actionName = strip_tags($result[2] ?: $config['default_action']);
            $actionName = $convert ? strtolower($actionName) : $actionName;
            // 设置当前请求的控制器、操作
            $request->controller(Loader::parseName($controller, 1))->action($actionName);
            // 监听module_init
            Hook::listen('module_init', $request);
            $instance = Loader::controller($controller, $config['url_controller_layer'], $config['controller_suffix'], $config['empty_controller']);
            if (is_null($instance)) {
                throw new HttpException(404, 'controller not exists:' . Loader::parseName($controller, 1));
            }
            // 获取当前操作名
            $action = $actionName . $config['action_suffix'];
            $vars   = [];
            if (is_callable([
                                $instance,
                                $action
                            ]
            )) {
                // 执行操作方法
                $call = [
                    $instance,
                    $action
                ];
            } elseif (is_callable([
                                      $instance,
                                      '_empty'
                                  ]
            )) {
                // 空操作
                $call = [
                    $instance,
                    '_empty'
                ];
                $vars = [$actionName];
            } else {
                // 操作不存在
                throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
            }
            Hook::listen('action_begin', $call);
            return self::invokeMethod($call, $vars);
        }

        /**
         * 调用反射执行类的方法 支持参数绑定
         * @access public
         * @param string|array $method 方法
         * @param array        $vars   变量
         * @return mixed
         */
        public static function invokeMethod($method, $vars = []) {
            if (is_array($method)) {
                $class   = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]);
                $reflect = new \ReflectionMethod($class, $method[1]);
            } else {
                // 静态方法
                $reflect = new \ReflectionMethod($method);
            }
            $args = self::bindParams($reflect, $vars);
            self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info');
            return $reflect->invokeArgs(isset($class) ? $class : null, $args);
        }

        /**
         * 调用反射执行类的实例化 支持依赖注入
         * @access public
         * @param string $class 类名
         * @param array  $vars  变量
         * @return mixed
         */
        public static function invokeClass($class, $vars = []) {
            $reflect     = new \ReflectionClass($class);
            $constructor = $reflect->getConstructor();
            if ($constructor) {
                $args = self::bindParams($constructor, $vars);
            } else {
                $args = [];
            }
            return $reflect->newInstanceArgs($args);
        }

        /**
         * 绑定参数
         * @access private
         * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类
         * @param array                                 $vars    变量
         * @return array
         */
        private static function bindParams($reflect, $vars = []) {
            if (empty($vars)) {
                // 自动获取请求变量
                if (Config::get('url_param_type')) {
                    $vars = Request::instance()->route();
                } else {
                    $vars = Request::instance()->param();
                }
            }
            $args = [];
            if ($reflect->getNumberOfParameters() > 0) {
                // 判断数组类型 数字数组时按顺序绑定参数
                reset($vars);
                $type   = key($vars) === 0 ? 1 : 0;
                $params = $reflect->getParameters();
                foreach ($params as $param) {
                    $args[] = self::getParamValue($param, $vars, $type);
                }
            }
            return $args;
        }

        /**
         * 获取参数值
         * @access private
         * @param \ReflectionParameter $param
         * @param array                $vars 变量
         * @param string               $type
         * @return array
         */
        private static function getParamValue($param, &$vars, $type) {
            $name  = $param->getName();
            $class = $param->getClass();
            if ($class) {
                $className = $class->getName();
                $bind      = Request::instance()->$name;
                if ($bind instanceof $className) {
                    $result = $bind;
                } else {
                    if (method_exists($className, 'invoke')) {
                        $method = new \ReflectionMethod($className, 'invoke');
                        if ($method->isPublic() && $method->isStatic()) {
                            return $className::invoke(Request::instance());
                        }
                    }
                    $result = method_exists($className, 'instance') ? $className::instance() : new $className;
                }
            } elseif (1 == $type && !empty($vars)) {
                $result = array_shift($vars);
            } elseif (0 == $type && isset($vars[$name])) {
                $result = $vars[$name];
            } elseif ($param->isDefaultValueAvailable()) {
                $result = $param->getDefaultValue();
            } else {
                throw new \InvalidArgumentException('method param miss:' . $name);
            }
            return $result;
        }

        /**
         * 执行函数或者闭包方法 支持参数调用
         * @access public
         * @param string|array|\Closure $function 函数或者闭包
         * @param array                 $vars     变量
         * @return mixed
         */
        public static function invokeFunction($function, $vars = []) {
            $reflect = new \ReflectionFunction($function);
            $args    = self::bindParams($reflect, $vars);
            // 记录执行信息
            self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info');
            return $reflect->invokeArgs($args);
        }

        /**
         * 设置当前请求的调度信息
         * @access public
         * @param array|string $dispatch 调度信息
         * @param string       $type     调度类型
         * @return void
         */
        public static function dispatch($dispatch, $type = 'module') {
            self::$dispatch = [
                'type' => $type,
                $type  => $dispatch
            ];
        }

        /**
         * 设置应用的路由检测机制
         * @access public
         * @param  bool $route 是否需要检测路由
         * @param  bool $must  是否强制检测路由
         * @return void
         */
        public static function route($route, $must = false) {
            self::$routeCheck = $route;
            self::$routeMust  = $must;
        }
    }
